# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-02-23 14:52:43
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-01 15:31:00

# **********
'''
学习将不同的几何变换应用到图像上，如平移、旋转、仿射变换等。
你会看到这些函数: cv.getPerspectiveTransform
OpenCV提供了两个转换函数 cv.warpAffine 和 cv.warpPerspective ，您可以使用它们进行各种转换。
cv.warpAffine 采用2x3转换矩阵，而 cv.warpPerspective 采用3x3转换矩阵作为输入。
'''

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)

# **********
'''
缩放
缩放是调整图像的大小, OpenCV带有一个函数 cv.resize()
图像的大小可以手动指定，也可以指定缩放比例。
也可使用不同的插值方法。
首选的插值方法是**cv.INTER_AREA**用于缩小，**cv.INTER_CUBIC（慢）和**cv.INTER_LINEAR**用于缩放。
默认情况下，出于所有调整大小的目的，使用的插值方法为**cv.INTER_LINEAR**。
'''
def Resize():
    img = cv.imread(imgpath('messi5.jpg'))
    # cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
    res = cv.resize(img,None,fx=2,fy=2,interpolation=cv.INTER_CUBIC)
    # 或者
    height, width = img.shape[:2]
    res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)
    cv.imshow('sri',img)
    cv.imshow('res',res)
    cv.waitKey(0)
    cv.destroyAllWindows()

# **********
'''
平移
平移是物体位置的移动。如果您知道在(x,y)方向上的位移，则将其设为(tx,ty)，你可以创建转换矩阵M
M=[[1,0,tx],
   [0,1,ty]]
您可以将其放入**np.float32**类型的Numpy数组中，并将其传递给**cv.warpAffine**函数。

下面偏移为(100, 50)的示例：
'''
def Move():
    img = cv.imread(imgpath('messi5.jpg'),0) # 0表示cv.COLOR_BGR2GRAY
    rows,cols = img.shape
    M = np.float32([[1,0,100],[0,1,50]])
    # [1,0,100]==横向移动，竖向不移动，移动距离为100
    # [0,1,50] ==横向不移动，竖向移动，移动距离为50
    # cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) -> dst
    dst = cv.warpAffine(img,M,(cols,rows)) # 相当于重新生成了一张图
    # cv.warpAffine 函数的第三个参数是输出图像的大小，其形式应为(width，height)。
    # 记住width =列数，height =行数。所以此处的 tuple形式为(cols,rows)
    cv.imshow('img',dst)
    cv.waitKey(0)
    cv.destroyAllWindows()

# **********
'''
旋转
图像旋转角度为 θ 是通过以下形式的变换矩阵实现的：
M = [[cosθ, -sinθ],
     [sinθ, cosθ]]
但是OpenCV提供了可缩放的旋转以及可调整的旋转中心，因此您可以在自己喜欢的任何位置旋转。
修改后的变换矩阵为
[[α,  β, (1-α)*center*x-β*center*y],
 [-β, α, β*center*x+(1-α)*center*y]]
其中 α = scale*cosθ, β = scale*sinθ
为了找到此转换矩阵，OpenCV提供了一个函数 cv.getRotationMatrix2D 。

以下示例将图像相对于中心旋转90度而没有任何缩放比例。
'''
def Rotation():
    img = cv.imread(imgpath('messi5.jpg'),0)
    rows,cols = img.shape
    # cols-1 和 rows-1 是坐标限制
    # cv.getRotationMatrix2D(center, angle, scale) -> retval
    M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1) # scale=1,表示无缩放
    dst = cv.warpAffine(img,M,(cols,rows))
    # 此函数cv.warpAffine的作用就是将原图img经过矩阵M的变换，并生成一个宽度为cols和高度为rows的新图
    # 所以理论上说是只要能找到变换矩阵，这个函数可以对原图img做任意变换
    cv.imshow('dst',dst)
    cv.waitKey(0)
    cv.destroyAllWindows()

# **********
'''
仿射变换
在仿射变换中，原始图像中的所有平行线在输出图像中仍将平行。
为了找到变换矩阵，我们需要输入图像中的三个点及其在输出图像中的对应位置。
然后cv.getAffineTransform将创建一个2x3矩阵，该矩阵将传递给cv.warpAffine。
'''
def Affine():
    img = cv.imread(imgpath('drawing.png'))
    rows,cols,ch = img.shape
    pts1 = np.float32([[50,50],[200,50],[50,200]])
    pts2 = np.float32([[10,100],[200,50],[100,250]])
    M = cv.getAffineTransform(pts1,pts2)
    dst = cv.warpAffine(img,M,(cols,rows))
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()

# **********
'''
透视变换
对于透视变换，您需要3x3变换矩阵。即使在转换后，直线也将保持直线。
要找到此变换矩阵，您需要在输入图像上有4个点，在输出图像上需要相应的点。
在这四个点中，其中三个不应共线。然后可以通过函数**cv.getPerspectiveTransform**找到变换矩阵。
然后将**cv.warpPerspective**应用于此3x3转换矩阵。
'''
def Perspective():
    img = cv.imread(imgpath('sudoku.png'))
    rows,cols,ch = img.shape # (563, 558, 3)
    pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
    pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])
    M = cv.getPerspectiveTransform(pts1,pts2)
    dst = cv.warpPerspective(img,M,(400,400)) # (cols,rows)
    plt.subplot(121),plt.imshow(img),plt.title('Input')
    plt.subplot(122),plt.imshow(dst),plt.title('Output')
    plt.show()


# **********
'''
什么是透视？什么是仿射？
https://blog.csdn.net/weixin_38406982/article/details/107291867
https://blog.csdn.net/Caesar6666/article/details/104158047
https://blog.csdn.net/flyyufenfei/article/details/80208361

** 仿射变换 **
仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程，转换过程坐标点的相对位置和属性不发生变换，
是一个线性变换，该过程只发生旋转和平移过程。它保持了二维图形的“平直性”和“平行性”
因此，一个平行四边形经过仿射变换后还是一个平行四边形。
所以，仿射= 旋转 + 平移

** 透视变换 **
透视变换是把一个图像投影到一个新的视平面的过程，
该过程包括：把一个二维坐标系转换为三维坐标系，然后把三维坐标系投影到新的二维坐标系。
该过程是一个非线性变换过程，因此，一个平行四边形经过透视变换后只得到四边形，但不平行。

粗糙的理解起来，
仿射应用于 “视觉效果看起来是二维的图片” 的变换
透视应用于 “视觉效果看起来是三维的图片” 的变换
'''

if __name__ == '__main__':
    # Resize() # 用python opencv来制作不同尺寸的图形也比较方便
    # Move()
    # Rotation()
    # Affine()
    Perspective()




